﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using Company.Common.Control;
using Company.IO.Interfaces;
using FileMode = Company.IO.Interfaces.FileMode;
using SearchOption = Company.IO.Interfaces.SearchOption;

namespace Company.IO
{
    public class FileSystemQueue<TQueueItem> : Common.Queue.Queue<TQueueItem> where TQueueItem : class
    {
        private const string _queueName = "FileSystemQueue";

        private readonly IFileSystem _fileSystem;
        private List<string> _fileNameCache;

        [DebuggerHidden]
        public FileSystemQueue(IFileSystem fileSystem)
        {
            _fileSystem = fileSystem;

            InitializeQueue();
        }

        #region Overrides of Queue<TQueueItem>

        [DebuggerHidden]
        [DebuggerStepThrough]
        protected sealed override void EnqueueItem(TQueueItem queueItem)
        {
            string fileName = CreateFileName();
            try
            {
                using (IStream fileStream = _fileSystem.CreateFile(fileName))
                {
                    using (var stream = new MemoryStream())
                    {
                        Serialize(queueItem, stream);
                        byte[] bytes = stream.ToArray();
                        fileStream.Write(bytes, 0, bytes.Length);
                    }
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
                throw;
            }
            _fileNameCache.Add(fileName);
        }

        [DebuggerHidden]
        [DebuggerStepThrough]
        protected sealed override bool TryDequeueItem(out TQueueItem queueItem)
        {
            queueItem = null;
            string fileName = _fileNameCache[0];
            try
            {
                using (IStream fileStream = _fileSystem.OpenFile(fileName, FileMode.Open))
                {
                    var buffer = new byte[fileStream.Length];
                    fileStream.Read(buffer, 0, (int) fileStream.Length);
                    using (var stream = new MemoryStream(buffer))
                    {
                        queueItem = Deserialize(stream);
                    }
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception);
            }
            finally
            {
                if (queueItem != null)
                {
                    Try.Action(() => _fileNameCache.RemoveAt(0),
                        new Catch[] {new Catch<Exception>(_ => Debug.WriteLine(_.Message))});
                    Try.Action(() => _fileSystem.DeleteFile(fileName),
                        new Catch[] {new Catch<Exception>(_ => Debug.WriteLine(_.Message))});
                }
            }
            return (queueItem != null);
        }

        protected virtual void Serialize(TQueueItem queueItem, MemoryStream stream)
        {
            var serializer = new XmlSerializer(typeof (TQueueItem));
            serializer.Serialize(stream, queueItem);
        }

        protected virtual TQueueItem Deserialize(MemoryStream stream)
        {
            TQueueItem queueItem = null;
            var serializer = new XmlSerializer(typeof (TQueueItem));
            queueItem = (TQueueItem) serializer.Deserialize(stream);
            return queueItem;
        }

        #endregion

        [DebuggerHidden]
        private string QueueName
        {
            get { return _queueName; }
        }

        [DebuggerHidden]
        private string GetQueueFolder()
        {
            string currentFolder = _fileSystem.GetFullPath(_fileSystem.Combine(_fileSystem.GetCurrentDirectory(), QueueName));
            if (!_fileSystem.Directory.Exists(currentFolder))
            {
                _fileSystem.Directory.CreateDirectory(currentFolder);
            }

            return currentFolder;
        }

        [DebuggerHidden]
        private void InitializeQueue()
        {
            string searchPattern = string.Format("*{0}", GetExtension());

            string[] files = _fileSystem.Directory.GetFiles(GetQueueFolder(), searchPattern,
                SearchOption.TopDirectoryOnly);
            _fileNameCache = new List<string>(files);
            _fileNameCache.Sort((x, y) => x.CompareTo(y));
        }

        [DebuggerHidden]
        private string GetExtension()
        {
            Type itemType = typeof (TQueueItem);
            return string.Format(".{0}", itemType.FullName);
        }

        [DebuggerHidden]
        private string CreateFileName()
        {
            string extension = GetExtension();
            string filePath = GetQueueFolder();
            string fileName =
                _fileSystem.Path.GetFullPath(string.Format("{0}\\{1}{2}", filePath, DateTime.Now.ToFileTimeUtc(),
                    extension));

            while (_fileSystem.File.Exists(fileName))
            {
                fileName =
                    _fileSystem.Path.GetFullPath(string.Format("{0}\\{1}{2}", filePath, DateTime.Now.ToFileTimeUtc(),
                        extension));
            }

            return fileName;
        }
    }
}